package show.lmm.oauth2server.core.conf;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.RequestMatcher;
import show.lmm.oauth2server.core.DataHandler;
import show.lmm.oauth2server.core.handle.OAuth2AuthenticationFailureHandler;
import show.lmm.oauth2server.core.handle.OAuth2AuthenticationSuccessHandler;
import show.lmm.oauth2server.core.util.IpUtils;
import show.lmm.oauth2server.service.AppInfoService;

import java.time.Duration;
import java.util.*;

/**
 * oauth2服务器配置
 */
@Configuration
public class OAuth2ServerConfig {

    public static void applyDefaultSecurity(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
                new OAuth2AuthorizationServerConfigurer();

        authorizationServerConfigurer
                .tokenEndpoint(tokenEndpoint ->
                        tokenEndpoint.accessTokenRequestConverter(request -> {
                                    // IP白名单校验
                                    String ip = IpUtils.getIp(request);
                                    String clientId = request.getParameter("client_id");
                                    if (Optional.ofNullable(clientId).isEmpty()) {
                                        throw new OAuth2AuthenticationException("非法请求，错误码：1001");
                                    }
                                    Collection<String> ipWhites = DataHandler.appIpWhites.get(clientId);
                                    if (Optional.ofNullable(ipWhites).isEmpty() || ipWhites.isEmpty()) {
                                        throw new OAuth2AuthenticationException("非法请求，错误码：1002");
                                    }
                                    if (ipWhites.contains(ip)) {
                                        return null;
                                    }
                                    throw new OAuth2AuthenticationException(String.format("ip: %s，不在白名单", ip));
                                })
                                // 自定义accessToken返回值
                                .accessTokenResponseHandler(new OAuth2AuthenticationSuccessHandler())

                ).clientAuthentication(clientAuthenticationConfigurer ->
                        clientAuthenticationConfigurer
                                // 自定义异常返回值
                                .errorResponseHandler(new OAuth2AuthenticationFailureHandler())
                );

        RequestMatcher endpointsMatcher = authorizationServerConfigurer
                .getEndpointsMatcher();

        http
                .securityMatcher(endpointsMatcher)
                .authorizeHttpRequests(authorize ->
                        authorize.anyRequest().authenticated()
                )
                .csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
                .apply(authorizationServerConfigurer);
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
                new OAuth2AuthorizationServerConfigurer();
        http.apply(authorizationServerConfigurer);
        applyDefaultSecurity(http);
        return http
                .exceptionHandling(exceptions -> exceptions.
                        authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
                ).build();
    }

    @Bean
    public RegisteredClientRepository registeredClientRepository(AppInfoService appInfoService) {
        List<RegisteredClient> registeredClients = new ArrayList<>();
        appInfoService.list().forEach(item -> {
            DataHandler.appIpWhites.put(item.getAppKey(), Arrays.asList(item.getIpWhites().split(",")));
            registeredClients.add(RegisteredClient.withId(UUID.randomUUID().toString())
                    .clientId(item.getAppKey())
                    .clientSecret(String.format("{noop}%s", item.getAppSecret()))
                    .clientAuthenticationMethods(s -> {
                        s.add(ClientAuthenticationMethod.CLIENT_SECRET_POST);
                        s.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
                    })
                    .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                    .scope("message.read")
                    .clientSettings(ClientSettings.builder()
                            .requireAuthorizationConsent(true)
                            .requireProofKey(false)
                            .build())
                    .tokenSettings(TokenSettings.builder()
                            // 生成不透明token
                            .accessTokenFormat(OAuth2TokenFormat.REFERENCE)
                            // 签名方式
                            .idTokenSignatureAlgorithm(SignatureAlgorithm.RS256)
                            // accessToken 有效期
                            .accessTokenTimeToLive(Duration.ofHours(1))
                            .reuseRefreshTokens(true)
                            .build())
                    .build());
        });
        return new InMemoryRegisteredClientRepository(registeredClients);
    }

    @Bean
    public AuthorizationServerSettings authorizationServerSettings() {
        return AuthorizationServerSettings.builder()
                .issuer("http://127.0.0.1:8081")
                .build();
    }
}
